package com.linbit.linstor.debug;

import com.linbit.linstor.annotation.Nullable;
import com.linbit.linstor.logging.ErrorReporter;
import com.linbit.linstor.logging.StdErrorReporter;
import com.linbit.linstor.security.AccessContext;

import javax.inject.Inject;

import java.io.File;
import java.io.FileFilter;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

public class CmdDisplayReportList extends BaseDebugCmd
{
    private enum InstanceSelector
    {
        INVALID,
        CURRENT,
        ALL
    }

    private static final String PRM_INSTANCE    = "INSTANCE";
    private static final String PRM_CURRENT     = "CURRENT";
    private static final String PRM_ALL         = "ALL";
    private static final String PRM_FILTER_NAME = "MATCHID";


    private static final Map<String, String> PARAMETER_DESCRIPTIONS = new TreeMap<>();

    static
    {
        PARAMETER_DESCRIPTIONS.put(
            PRM_INSTANCE,
            "Selects the instance for which reports should be listed\n" +
            "    CURRENT\n" +
            "        List reports generated by the current instance\n" +
            "    ALL\n" +
            "        List all reports\n"
        );
        PARAMETER_DESCRIPTIONS.put(
            PRM_FILTER_NAME,
            "Filter pattern to apply to the report ID\n" +
            "Reports with a matching report ID will be displayed"
        );
    }

    private final ErrorReporter errorReporter;

    @Inject
    public CmdDisplayReportList(ErrorReporter errorReporterRef)
    {
        super(
            new String[]
            {
                "DspRptLst"
            },
            "Display report list",
            "Displays the list of saved error and problem reports",
            PARAMETER_DESCRIPTIONS,
            null
        );
        errorReporter = errorReporterRef;
    }

    @Override
    public void execute(
        PrintStream debugOut,
        PrintStream debugErr,
        AccessContext accCtx,
        Map<String, String> parameters
    )
        throws Exception
    {
        InstanceSelector slctInst = InstanceSelector.CURRENT;

        String prmInstance = parameters.get(PRM_INSTANCE);
        if (prmInstance != null)
        {
            if (prmInstance.equalsIgnoreCase(PRM_CURRENT))
            {
                slctInst = InstanceSelector.CURRENT;
            }
            else
            if (prmInstance.equalsIgnoreCase(PRM_ALL))
            {
                slctInst = InstanceSelector.ALL;
            }
            else
            {
                slctInst = InstanceSelector.INVALID;
                printError(
                    debugErr,
                    "The value specified for the " + PRM_INSTANCE + " parameter is invalid",
                    null,
                    "Specify a valid value for the " + PRM_INSTANCE + " parameter.\n" +
                    "Valid values are:\n" +
                    "    " + PRM_CURRENT + "\n" +
                    "    " + PRM_ALL,
                    "The specified value was '" + prmInstance + "'"
                );
            }
        }

        try
        {
            if (slctInst == InstanceSelector.ALL || slctInst == InstanceSelector.CURRENT)
            {
                String matchPattern = parameters.get(PRM_FILTER_NAME);
                File reportDir = errorReporter.getLogDirectory().toFile();
                File[] reportFileList;
                if (slctInst == InstanceSelector.CURRENT)
                {
                    reportFileList = reportDir.listFiles(
                        new ReportFileFilter(errorReporter.getInstanceId(), matchPattern)
                    );
                }
                else
                {
                    reportFileList = reportDir.listFiles(new ReportFileFilter(null, matchPattern));
                }
                // Objects of type File are supposed to sort lexicographically,
                // some details are platform-dependent (e.g., case sensitivity)
                Arrays.sort(reportFileList);

                int pfxLength = StdErrorReporter.RPT_PREFIX.length();
                int sfxLength = StdErrorReporter.RPT_SUFFIX.length();
                int minLength = pfxLength + sfxLength;

                for (File reportFile : reportFileList)
                {
                    String reportId = "<unparsable>";
                    String fileName = reportFile.getName();
                    int nameLength = fileName.length();
                    if (nameLength >= minLength)
                    {
                        reportId = fileName.substring(pfxLength, nameLength - sfxLength);
                    }
                    debugOut.printf("%-23s (%s)\n", reportId, fileName);
                }
                if (reportFileList.length == 0)
                {
                    debugOut.println("No saved reports were found.");
                }
                else
                {
                    printSectionSeparator(debugOut);
                    if (reportFileList.length == 1)
                    {
                        debugOut.println("1 saved report");
                    }
                    else
                    {
                        debugOut.println(reportFileList.length + " saved reports");
                    }
                }

            }
        }
        catch (PatternSyntaxException patternExc)
        {
            printError(
                debugOut,
                "The regular expression specified for the parameter " + PRM_FILTER_NAME + " is not valid.",
                patternExc.getMessage(),
                "Reenter the command using the correct regular expression syntax for the " + PRM_FILTER_NAME +
                " parameter.",
                null
            );
        }
    }

    private static class ReportFileFilter implements FileFilter
    {
        private final String prefixMatch = StdErrorReporter.RPT_PREFIX.toUpperCase();
        private final String suffixMatch = StdErrorReporter.RPT_SUFFIX.toUpperCase();
        private final int pfxLength = prefixMatch.length();
        private final int sfxLength = suffixMatch.length();
        private final int minLength = pfxLength + sfxLength;

        private final @Nullable String instanceId;
        private final @Nullable Matcher idMatcher;

        ReportFileFilter(@Nullable String instanceIdRef, String matchPattern)
        {
            instanceId = instanceIdRef;
            if (matchPattern != null)
            {
                Pattern idPattern = Pattern.compile(matchPattern, Pattern.CASE_INSENSITIVE);
                idMatcher = idPattern.matcher("");
            }
            else
            {
                idMatcher = null;
            }
        }

        @Override
        public boolean accept(File selectedFile)
        {
            boolean result = false;
            String fileName = selectedFile.getName().toUpperCase();
            if (fileName.startsWith(prefixMatch) &&
                fileName.endsWith(suffixMatch))
            {
                if (instanceId != null || idMatcher != null)
                {
                    int nameLength = fileName.length();
                    if (nameLength >= minLength)
                    {
                        String reportId = fileName.substring(pfxLength, nameLength - sfxLength);

                        // Instance filter matches always when no filter is set
                        boolean instanceMatches = instanceId == null;
                        if (instanceId != null)
                        {
                            int splitIdx = reportId.lastIndexOf('-');
                            if (splitIdx != -1)
                            {
                                String reportInstanceId = reportId.substring(0, splitIdx);
                                instanceMatches = instanceId.equalsIgnoreCase(reportInstanceId);
                            }
                        }

                        // Pattern filter matches always when no filter is set
                        boolean patternMatches = idMatcher == null;
                        if (instanceMatches && idMatcher != null)
                        {
                            idMatcher.reset(reportId);
                            patternMatches = idMatcher.find();
                        }

                        result = instanceMatches && patternMatches;
                    }
                }
                else
                {
                    result = true;
                }
            }
            return result;
        }
    }
}
